/**
 * @fileOverview 单元测试
 * @author goodzsq@gmail.com
 */

/**
 * 单元测试类
 * @param {String} desp 单元测试描述
 * @param {Function} test 测试体函数
 * @param {Function} start 开始测试前的回调
 * @param {Function} end 测试结束后的回调
 * @returns {UnitTest}
 */
var UnitTest = function(desp, test, start, end) {
    /** 测试结果统计 */
    this.stat = {result: [], desp: desp, ok: 0, fail: 0, total: 0};
    if (typeof start === 'function') {
        start.call(this);
    }
    if (typeof test === 'function') {
        this.stat.start = Date.now();
        test.call(this);
        this.stat.end = Date.now();
        this.stat.timeSpan = this.stat.end - this.stat.start;
    }
    if (typeof end === 'function') {
        end.call(this);
    }
};

/**
 * 测试表达式是否为真
 * @param {Boolean} express 表达式
 * @param {String} desp 描述语句
 */
UnitTest.prototype.test = function(express, desp) {
    this.stat.total++;
    if (express === true) {
        this.stat.ok++;
        this.stat.result.push({name: desp, result: 'ok'});
        return true;
    } else {
        this.stat.fail++;
        this.stat.result.push({name: desp, result: 'fail'});
        console.error('测试失败', desp);
    }
};

/**
 * 测试2个对象内容是否一样
 * @param {Object} a
 * @param {Object} b
 * @param {String} desp
 */
UnitTest.prototype.testObject = function(a, b, desp) {
    if (Object.prototype.toString.call(a) !== '[object Object]')
        return this.test(false, desp);
    if (Object.prototype.toString.call(b) !== '[object Object]')
        return this.test(false, desp);
    return this.test(gg.equal(a, b), desp);
};

/**
 * 测试2个数组内容是否一样
 * @param {Object} a
 * @param {Object} b
 * @param {String} desp
 */
UnitTest.prototype.testArray = function(a, b, desp) {
    if (Object.prototype.toString.call(a) !== '[object Array]')
        return this.test(false, desp);
    if (Object.prototype.toString.call(b) !== '[object Array]')
        return this.test(false, desp);
    return this.test(gg.equal(a, b), desp);
};

/**
 * 测试集合 不考虑顺序的数组和重复元素
 * @param {Object} a
 * @param {Object} b
 * @param {String} desp
 */
UnitTest.prototype.testSet = function(a, b, desp) {
    if (Object.prototype.toString.call(a) !== '[object Array]')
        return this.test(false, desp);
    if (Object.prototype.toString.call(b) !== '[object Array]')
        return this.test(false, desp);
    return this.test(gg.equal(_.union(a), _.union(a, b)), desp);
};

/**
 * 测试函数的执行结果
 */
UnitTest.prototype.testFunction = function(obj, method, args, result, desp) {
    this.stat.total++;
    if (obj && typeof method === 'string') {
        method = obj[method];
    }
    try {
        if (typeof method === 'function' && method.apply(obj, args) === result) {
            this.stat.ok++;
            this.stat.result.push({name: desp, result: 'ok'});
        } else {
            this.stat.fail++;
            this.stat.result.push({name: desp, result: 'fail'});
        }
    }
    catch (e) {
        this.stat.fail++;
        this.stat.result.push({name: desp, result: 'fail', stack: e.stack});
    }
};

/**
 * 测试函数的执行应该抛出一个异常
 */
UnitTest.prototype.testExcept = function(obj, method, args, desp) {
    this.stat.total++;
    if (obj && typeof method === 'string') {
        method = obj[method];
    }
    try {
        method.apply(obj, args);
        this.stat.fail++;
        this.stat.result.push({name: desp, result: 'fail'});
    }
    catch (e) {
        this.stat.ok++;
        this.stat.result.push({name: desp, result: 'ok', stack: e.stack});
    }
};

/**
 * 测试回调
 * @param {String} desp 测试描述
 * @param {Function} callback 测试回调函数
 * @param {Number} timeout 超时未执行回调
 * @return {Function} 测试回调
 */
UnitTest.prototype.testCallback = function(desp, callback, timeout) {
    this.stat.total++;
    this.stat.result.push({name: desp, result: 'waiting'});
    var testId = this.stat.result.length - 1;
    var self = this;
    var fun = _.once(function(){
        if(callback.apply(self, arguments) === true){
            self.stat.ok++;
            self.stat.result[testId].result = 'ok';
        }else{
            self.stat.fail++;
            self.stat.result[testId].result = 'fail';
            console.error('测试失败', desp);
        }
    });
    _.delay(fun, timeout || 1000, 'test_timeout');
    return fun;
};

//待测试队列
UnitTest._queue = [];

/**
 * 将测试套件加入测试队列，适用于所有环境
 * @param {String} desp 测试描述
 * @param {Function} test 测试体
 * @param {Function} start 测试准备
 * @param {Function} end 测试结束清理
 */
UnitTest.add = function(desp, test, start, end) {
    UnitTest._queue.push([desp, test, start, end]);
};

/**
 * 将测试套件加入测试队列，仅适用于浏览器
 * @param {String} desp 测试描述
 * @param {Function} test 测试体
 * @param {Function} start 测试准备
 * @param {Function} end 测试结束清理
 */
UnitTest.addToBrowser = function(desp, test, start, end) {
    if (gg.runAt.browser) {
        UnitTest._queue.push([desp, test, start, end]);
    }
};
/**
 * 将测试套件加入测试队列，仅适用于服务端
 * @param {String} desp 测试描述
 * @param {Function} test 测试体
 * @param {Function} start 测试准备
 * @param {Function} end 测试结束清理
 */
UnitTest.addToServer = function(desp, test, start, end) {
    if (gg.runAt.server) {
        UnitTest._queue.push([desp, test, start, end]);
    }
};
/**
 * 将测试套件加入测试队列，仅适用于客户端JS绑定
 * @param {String} desp 测试描述
 * @param {Function} test 测试体
 * @param {Function} start 测试准备
 * @param {Function} end 测试结束清理
 */
UnitTest.addToNative = function(desp, test, start, end) {
    if (gg.runAt.native) {
        UnitTest._queue.push([desp, test, start, end]);
    }
};
/**
 * 执行一个测试套件
 * @param {String} desp 测试描述
 * @param {Function} test 测试函数
 * @param {Function} start 测试前的准备函数
 * @param {Function} end 测试后的清理函数
 * @returns {Object} 测试结果
 */
UnitTest.run = function(desp, test, start, end) {
    var unittest = new UnitTest(desp, test, start, end);
    return unittest.stat;
};

/**
 * 开始执行所有测试套件
 * @returns {Array} 测试结果
 */
UnitTest.runAll = function() {
    console.log('开始执行测试...');
    var result = [];
    for (var i = 0; i < UnitTest._queue.length; i++) {
        try {
            var r = UnitTest.run.apply(this, UnitTest._queue[i]);
            console.log(r);
            result.push(r);
        } catch (e) {
            console.error('测试套件执行失败:' + UnitTest._queue[i][0], e.stack);
            result.push({desp: UnitTest._queue[i][0], result: 'fail', stack: e.stack});
        }
    }
    return result;
};

/**
 * 清空测试队列，重新准备测试
 */
UnitTest.reset = function() {
    UnitTest._queue = [];
};

/** @fileend */

//unittest
UnitTest.add('测试单元测试的单元测试',
        function() {
            this.test((1 + 2) === 3, '1+2=3');
            this.test((2 + 2) !== 3, '2+2=3');
            this.test(8 == '8', '8=="8"');
            this.test(8 !== '8', '8!=="8"');
            this.testFunction(this, "add", [1, 2], 3, '测试add函数的执行');
            this.testExcept(this, this.error, 'xxx', '这个函数应该扔出异常');
            this.testObject({a: 1, b: 2}, {b: 2, a: 1}, '对象相等');
            this.testObject({a: 1, b: {a: 1, b: 2}}, {b: {b: 2, a: 1}, a: 1}, '对象相等深度测试');
            this.test(!gg.equal({a: 1, b: 2}, {a: 1, b: 2, c: 3}), '对象不相等');
            this.testArray([1, 2, 3], [1, 2, 3], '数组相等');
            this.testArray([[1, 2], {a: 1, b: [3, 4]}], [[1, 2], {a: 1, b: [3, 4]}], '深度数组相等');
            this.test(!gg.equal([2, 1, 3], [1, 2, 3]), '数组不相对');
            var cb = this.testCallback('测试回调函数', function(){
                if(arguments[0] === 'test_timeout'){
                    return false;
                }else{
                    return true;
                }
            }, 3000);
            _.delay(cb, 2000);
            var cb = this.testCallback('测试回调超时', function(){
                if(arguments[0] === 'test_timeout'){
                    return true;
                }else{
                    return false;
                }
            }, 1000);
            _.delay(cb, 2000);
        },
        function() {
            //测试开始前的一些设置工作
            this.add = function(a, b) {
                return a + b;
            };
            this.error = function(msg) {
                gg.error(msg);
            };
        },
        function() {
            //测试结束后的一些清理工作
        }
);

UnitTest.addToBrowser('这是只运行在浏览器环境上的单元测试',
        function() {
            this.test(true);
        }
);

UnitTest.addToServer('这是只运行在服务器环境上的单元测试',
        function() {
            this.test(true);
        }
);

UnitTest.addToNative('这是只运行在客户端绑定JS环境上的单元测试',
        function() {
            this.test(true);
        }
);

UnitTest.add('简单测试模板',
        function() {
            this.test(true, '测试说明');
        }
);

UnitTest.add('完整测试模板',
        function() {
            this.test(true, '测试说明');
        },
        function() {
            //初始化
        },
        function() {
            //测试完毕的清理
        }
);

module.exports = UnitTest;